PySPD

  • PySPD is a small library I created to assist in the creation of small linear programming models with reserve constraints.
  • It serves as an abstraction layer around a linear program
  • Use to make creating models easier, can also chuck in some post processing as well

In [1]:
# Import Modules
import numpy as np
import pulp
from pyspd import *

Creating a Model

  • To create a model we need to define some basic parameters, there is a fixed order for doing this.
  • A model consists of companies, reserve zones, nodes and branches
  • You need to define reserve zones first
  • Nodes can then be assigned to a particular reserve zone
  • Branches may then be defined between nodes
  • This ordering ensures that the system is fully defined in an appropriate manner

In [2]:
# Define a System Operator
operator = SystemOperator()

# Two Reserve Zones
north_island = ReserveZone("NorthIsland", operator)
south_island = ReserveZone("SouthIsland", operator)

# Define a company of interest
meridian = Company("Meridian")

# I typically define what I'm holding constant as the market
market = Company("Market")

# Define two nodes, benmore and Haywards
benmore = Node("Benmore", operator, south_island, demand=140)
haywards = Node("Haywards", operator, north_island, demand=150)

# Define a Transmission link, make it a risk setter
hvdc = Branch(operator, benmore, haywards, capacity=1000, risk=True)

Creating Stations

  • We then define a series of stations or interruptible load providers.
  • A station may be either energy or energy and reserve.
  • An interruptible load provider is just reserve

In [3]:
# Define two stations and two interruptible load providers.
manapouri = Station("Manapouri", operator, benmore, meridian, capacity=400)
roxburgh = Station("Roxburgh", operator, benmore, market, capacity=400)
tiwai = InterruptibleLoad("Tiwai", operator, benmore, market)
nzst = InterruptibleLoad("NZST", operator, haywards, market)

Apply Offers

  • We can then add energy and reserve offers to the units.
  • Generator Reserve offers are price - capacity - proportion
  • Energy offers are price - capacity

E.g.

$ g_i \le g_{i,max} $

$ r_i \le r_{i, max} $

$ r_i \le p_i g_i $

$ r_i + g_i \le g_{i, capacity} $


In [4]:
# Manapouri offers
manapouri.add_reserve_offer(100, 300, 1.0)
manapouri.add_energy_offer(25, 125)

# Roxburgh
roxburgh.add_energy_offer(30, 250)
roxburgh.add_reserve_offer(15, 100, 1)

# Tiwai and NZST
tiwai.add_reserve_offer(15, 80)
nzst.add_reserve_offer(150, 500)


Out[4]:
<pyspd.actors.InterruptibleLoad at 0x456b550>

Apply Special Sauce

  • Now we do a little bit of innovation and we apply an iterator
  • The iterator varies the reserve price across user defined parameters
  • This is all solved simultaneously within the LP

In [5]:
# Vary the reserve price at Manapouri between 0 and 200 (the 210 won't be included)
# Vary in increments of $10/MWh
operator.create_iterator(manapouri, "energy_price", np.arange(0, 100, 10))


Out[5]:
<pyspd.actors.SystemOperator at 0x456b950>

Solving the Model

  • Solve the model and create an analysis object
  • This is a generation only LP

$ min \sum p_i g_i + \sum p_i r_i $

subject to ...


In [6]:
# Create the Solver
lpsolver = SPDModel(operator)
# Solve the LP
lpsolver.full_run()
# Create an Analytics Function (Yes I know this is convoluted)
ana = Analytics(lpsolver)

Lets have a look at some cool things

We'll start with the Energy and Reserve Dispatch.

  • These are all done as a function of the Manapouri Reserve Price iterator we specified.

In [7]:
ana.create_dispatch_df()
ana.final_dispatch_df


Out[7]:
Manapouri Energy Total Manapouri Reserve Total NZST Reserve Total Roxburgh Energy Total Roxburgh Reserve Total Tiwai Reserve Total
Manapouri Energy Price
0 125 0 150 165 100 65
10 125 0 150 165 100 65
20 125 0 150 165 100 65
30 125 0 150 165 100 65
40 125 0 150 165 100 65
50 110 0 150 180 100 80
60 110 0 150 180 100 80
70 110 0 150 180 100 80
80 110 0 150 180 100 80
90 110 0 150 180 100 80

Or we could look at prices


In [8]:
ana.create_price_df()
ana.final_price_df


Out[8]:
Benmore Energy Price Haywards Energy Price NorthIsland Reserve Price SouthIsland Reserve Price
Manapouri Energy Price
0 45 195 150 15
10 45 195 150 15
20 45 195 150 15
30 45 195 150 15
40 45 195 150 15
50 50 200 150 20
60 60 210 150 30
70 70 220 150 40
80 80 230 150 50
90 90 240 150 60

Or look at the North and South Island Prices and Risks as a function of the Manapouri Energy Price


In [9]:
ana.create_reserve_df()
ana.reserve_df


Out[9]:
NorthIsland Reserve Price SouthIsland Reserve Price NorthIsland Reserve Risk SouthIsland Reserve Risk
Manapouri Energy Price
0 150 15 150 165
10 150 15 150 165
20 150 15 150 165
30 150 15 150 165
40 150 15 150 165
50 150 20 150 180
60 150 30 150 180
70 150 40 150 180
80 150 50 150 180
90 150 60 150 180

Other Functionality

  • I haven't been able to devote any time to this recently so not much else has progressed.
  • Could likely build in some counter factuals
  • Potentially Agents?
  • There's a couple nice libraries with built in support for learning algorithms etc
  • can also do these as plots...

In [14]:
# Show the increase in the South Island Reserve Price as a function of the Manapouri Energy price
ana.reserve_df["SouthIsland Reserve Price"].plot()


Out[14]:
<matplotlib.axes.AxesSubplot at 0x4e0c610>

Can also show the profits at the various units


In [22]:
# Manapouri
ana.create_master()
manapouri.calculate_profits()
manapouri.total_profit.plot()


Out[22]:
<matplotlib.axes.AxesSubplot at 0x523ba90>

In [23]:
roxburgh.calculate_profits()
roxburgh.total_profit.plot()


Out[23]:
<matplotlib.axes.AxesSubplot at 0x5497b90>